异步迭代器

1. 异步迭代器简介

在现代编程中,异步操作变得越来越常见,尤其是在处理网络请求、文件读取等I/O密集型任务时。JavaScript 提供了一种强大的机制来处理异步操作,即异步迭代器(Async Iterators)。异步迭代器允许我们以异步的方式遍历数据集合,这在处理流式数据或需要等待异步操作完成的情况下非常有用。

1.1 什么是异步迭代器?

异步迭代器是一种特殊的迭代器,它返回一个 Promise 对象。与同步迭代器不同,异步迭代器允许我们在每次迭代时等待异步操作完成。异步迭代器通常用于处理需要等待的异步数据源,例如从网络获取数据或读取大文件。

1.2 异步迭代器与同步迭代器的区别

  • 同步迭代器:返回一个值或 undefined。
  • 异步迭代器:返回一个 Promise,该 Promise 解析为一个值或 undefined。

2. 创建异步迭代器

在 TypeScript 中,我们可以通过实现 AsyncIterable 接口来创建异步迭代器。AsyncIterable 接口定义了一个 [Symbol.asyncIterator] 方法,该方法返回一个 AsyncIterator 对象。

2.1 实现 AsyncIterable 接口

interface AsyncIterable<T> {
  [Symbol.asyncIterator](): AsyncIterator<T>;
}

interface AsyncIterator<T> {
  next(): Promise<IteratorResult<T>>;
}

interface IteratorResult<T> {
  value: T;
  done: boolean;
}

2.2 创建一个简单的异步迭代器

下面是一个简单的异步迭代器示例,它每隔一秒返回一个数字:

class AsyncNumberIterator implements AsyncIterable<number> {
  private current = 0;

  [Symbol.asyncIterator](): AsyncIterator<number> {
    return {
      next: async () => {
        await new Promise(resolve => setTimeout(resolve, 1000));
        if (this.current >= 5) {
          return { value: undefined, done: true };
        }
        return { value: this.current++, done: false };
      }
    };
  }
}

async function main() {
  const iterator = new AsyncNumberIterator();
  for await (const num of iterator) {
    console.log(num);
  }
}

main();

在这个示例中,我们创建了一个 AsyncNumberIterator 类,它实现了 AsyncIterable 接口。[Symbol.asyncIterator] 方法返回一个 AsyncIterator 对象,该对象的 next 方法返回一个 Promise,该 Promise 在 1 秒后解析为一个数字。

3. 使用 for await...of 循环

for await...of 循环是 JavaScript 中用于遍历异步迭代器的一种语法。它允许我们在每次迭代时等待异步操作完成。

3.1 for await...of 语法

for await (const item of asyncIterable) {
  // 处理 item
}

3.2 示例:使用 for await...of 遍历异步迭代器

async function main() {
  const iterator = new AsyncNumberIterator();
  for await (const num of iterator) {
    console.log(num);
  }
}

main();

在这个示例中,我们使用 for await...of 循环遍历 AsyncNumberIterator 实例。每次迭代时,for await...of 循环会等待 next 方法返回的 Promise 解析,然后再继续下一次迭代。

4. 实践练习

4.1 练习:创建一个异步文件读取迭代器

编写一个异步迭代器,它从文件中逐行读取内容,并在每次迭代时返回一行内容。

import * as fs from 'fs';
import * as readline from 'readline';

class AsyncFileIterator implements AsyncIterable<string> {
  private fileStream: fs.ReadStream;
  private reader: readline.Interface;

  constructor(filePath: string) {
    this.fileStream = fs.createReadStream(filePath);
    this.reader = readline.createInterface({
      input: this.fileStream,
      crlfDelay: Infinity
    });
  }

  [Symbol.asyncIterator](): AsyncIterator<string> {
    return this.reader[Symbol.asyncIterator]();
  }
}

async function main() {
  const filePath = 'path/to/your/file.txt';
  const iterator = new AsyncFileIterator(filePath);
  for await (const line of iterator) {
    console.log(line);
  }
}

main();

在这个练习中,我们创建了一个 AsyncFileIterator 类,它使用 fs 模块和 readline 模块逐行读取文件内容。[Symbol.asyncIterator] 方法返回 readline.Interface 的异步迭代器。

4.2 练习:创建一个异步生成器

编写一个异步生成器函数,它每隔一秒生成一个随机数,并返回这些随机数。

async function* asyncRandomNumbers() {
  while (true) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield Math.random();
  }
}

async function main() {
  for await (const num of asyncRandomNumbers()) {
    console.log(num);
  }
}

main();

在这个练习中,我们使用 async function* 语法定义了一个异步生成器函数 asyncRandomNumbers。该函数每隔一秒生成一个随机数,并使用 yield 关键字返回这些随机数。

5. 总结

异步迭代器是处理异步数据源的强大工具。通过实现 AsyncIterable 接口,我们可以创建自定义的异步迭代器,并使用 for await...of 循环遍历这些迭代器。异步迭代器在处理流式数据、网络请求和文件读取等场景中非常有用。

通过本教程的学习,你应该能够理解异步迭代器的基本概念,并能够创建和使用自定义的异步迭代器。希望你能通过实践练习进一步巩固所学知识,并在实际项目中应用这些概念。